package com.netstar.process;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import com.netstar.util.EvtLog;

import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;

public class ProcessWatcher implements Handler.Callback {

	private static final String TAG = "ProcessWatcher";

	private static final long CHECK_INTERVAL = 3000;

	private static ProcessWatcher sWatcher;

	private boolean mIsWatching;

	private Handler mWatchHandler;

	private ProcessListener mListener;

	private WeakReference<Context> mContext;

	private HashSet<Integer> mWatcherList;

	private HashMap<Integer, ActivityManager.RunningAppProcessInfo> mLastRunningApps;

	public static ProcessWatcher getDefault(Context context) {
		if (sWatcher == null) {
			sWatcher = new ProcessWatcher(context);
		}
		return sWatcher;
	}

	private ProcessWatcher(Context context) {
		mContext = new WeakReference<Context>(context);
		mWatcherList = new HashSet<Integer>();
		mLastRunningApps = new HashMap<Integer, ActivityManager.RunningAppProcessInfo>();
	}

	public synchronized void watch(Integer uid) {
		if (null != uid) {
			mWatcherList.add(uid);
		}

		if (null != mWatchHandler) {
			mWatchHandler.removeMessages(0);
			mWatchHandler.sendEmptyMessage(0);
		}
	}

	public void setProcessListener(ProcessListener listener) {
		mListener = listener;
	}

	public synchronized void unwatch(Integer uid) {
		if (null != uid) {
			mWatcherList.remove(uid);
		}

		if (null != mWatchHandler) {
			mWatchHandler.removeMessages(0);
			mWatchHandler.sendEmptyMessage(0);
		}
	}

	public synchronized void startWatch() {
		if (mIsWatching) {
			return;
		}
		final Thread t = new Thread("ProcessWatcherThread") {
			@Override
			public void run() {
				Looper.prepare();

				mWatchHandler = new Handler(Looper.myLooper(), ProcessWatcher.this);

				synchronized (ProcessWatcher.this) {
					ProcessWatcher.this.notifyAll();
				}
				Looper.loop();
			};
		};
		t.start();
		if (mWatchHandler == null) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}

		mWatchHandler.sendEmptyMessage(0);

		mIsWatching = true;
	}

	public boolean handleMessage(Message msg) {
		Context context = mContext.get();
		if (null == context) {
			throw new NullPointerException(
					"Context is recycled, you'd better register it on context which has application lifecycle.");
		}
		if (mWatcherList.size() == 0) {
			return true;
		}

		ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
		List<ActivityManager.RunningAppProcessInfo> appList = am.getRunningAppProcesses();
		if (null == appList || appList.size() == 0) {
			return true;
		}
		final HashMap<Integer, ActivityManager.RunningAppProcessInfo> nowRunningApps = new HashMap<Integer, ActivityManager.RunningAppProcessInfo>(
				appList.size());
		for (ActivityManager.RunningAppProcessInfo appInfo : appList) {
			nowRunningApps.put(appInfo.uid, appInfo);
		}
		synchronized (this) {
			Iterator<Integer> iterator = mWatcherList.iterator();
			PackageManager pm = context.getPackageManager();
			while (iterator.hasNext()) {
				Integer uid = iterator.next();
				ActivityManager.RunningAppProcessInfo last = mLastRunningApps.get(uid);
				ActivityManager.RunningAppProcessInfo now = nowRunningApps.get(uid);
				if (last == null && now != null) {
					String[] pkgs = now.pkgList;
					int l = pkgs.length;
					ProcessInfo info = new ProcessInfo();
					String[] appNames = new String[l];
					for (int i = 0; i < l; i++) {
						String pkg = pkgs[i];
						try {
							ApplicationInfo app = pm.getApplicationInfo(pkg, 0);
							appNames[i] = app.loadLabel(pm).toString();
						} catch (PackageManager.NameNotFoundException e) {
						}
					}
					info.appNames = appNames;
					info.uid = now.uid;
					info.processName = now.processName;
					if (null != mListener) {
						mListener.onProcessStarted(info);
					}
				} else if (last != null && now == null) {
					String[] pkgs = last.pkgList;
					int l = pkgs.length;
					ProcessInfo info = new ProcessInfo();
					String[] appNames = new String[l];
					for (int i = 0; i < l; i++) {
						String pkg = pkgs[i];
						try {
							ApplicationInfo app = pm.getApplicationInfo(pkg, 0);
							appNames[i] = app.loadLabel(pm).toString();
						} catch (PackageManager.NameNotFoundException e) {
						}
					}
					info.appNames = appNames;
					info.uid = last.uid;
					info.processName = last.processName;
					if (null != mListener) {
						mListener.onProcessStopped(info);
					}
				} else {
					// Ignore
				}
			}

			mLastRunningApps = nowRunningApps;
		}

		mWatchHandler.sendEmptyMessageDelayed(0, CHECK_INTERVAL);

		return true;
	}

	public synchronized void stopWatch() {
		if (!mIsWatching) {
			return;
		}

		mWatchHandler.getLooper().quit();
		mIsWatching = false;
	}

	public synchronized void pause() {
		if (!mIsWatching) {
			EvtLog.e(TAG, "startWatch not called");
			return;
		}
		if (null != mWatchHandler) {
			mWatchHandler.removeMessages(0);
		}
	}

	public synchronized void resume() {
		if (!mIsWatching) {
			EvtLog.e(TAG, "startWatch not called");
			return;
		}
		if (null != mWatchHandler) {
			mWatchHandler.removeMessages(0);
			mWatchHandler.sendEmptyMessage(0);
		}
	}

	public boolean isWatching() {
		return mIsWatching;
	}
}
